/*
 * #%L
 * SwiftUIWeb Maven Webapp
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2011 - 2012 Linkwithweb
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package com.linkwithweb.products.swiftui.controller;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.ModelAndView;

import com.linkwithweb.products.swiftui.model.PluginResponseContext;
import com.linkwithweb.products.swiftui.plugins.AbstractSwiftUIPlugin;
import com.linkwithweb.products.swiftui.plugins.PluginInstanceHolder;
import com.linkwithweb.products.swiftui.plugins.SwiftUIPluginContext;

/**
 * @author ashwin kumar
 * 
 */
@Controller
public class PluginController extends AbstractController {

	@Autowired
	@Qualifier("marshallingHttpMessageConverter")
	private HttpMessageConverter httpMessageConverter;

	private HttpMessageConverter<?>[] messageConverters;

	/**
	 * (non-Javadoc)
	 * For now Adding JSON Converter manually
	 * 
	 * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#getMessageConverters()
	 */
	public HttpMessageConverter<?>[] getMessageConverters() {
		if (messageConverters == null || messageConverters.length == 0) {
			messageConverters = new HttpMessageConverter[1];
			messageConverters[0] = httpMessageConverter;
		}
		return messageConverters;
	}

	public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
		this.messageConverters = messageConverters;
	}

	/**
	 * Constructor
	 */
	public PluginController() {
	}

	/**
	 * @param httpServletRequest
	 * @param httpServletResponse
	 * @return
	 */
	@RequestMapping("/pluginController/{pluginId}/{pluginAction}")
	public ModelAndView pluginController(@PathVariable(value = "pluginId") String pluginId,
			@PathVariable(value = "pluginAction") String pluginAction, HttpServletRequest httpServletRequest,
			HttpServletResponse httpServletResponse) {
		PluginResponseContext pluginResponseContext = new PluginResponseContext();
		Map<String, Object> paramMap = new HashMap<String, Object>();
		paramMap.put("responseContext", pluginResponseContext);

		/**
		 * First verify if plugin id is present in Request
		 */
		if (StringUtils.isBlank(pluginId)) {
			pluginResponseContext.getMessages().add(
					getMessageSource().getMessage("exception.pluigin.not.found", new Object[] {}, Locale.ENGLISH));
			return new ModelAndView("management/error_messages", paramMap);
		}

		/**
		 * Now verify if we have the plugin present in system.
		 */
		AbstractSwiftUIPlugin swiftUIPlugin = PluginInstanceHolder.getInstance().getPlugin(pluginId);
		if (swiftUIPlugin == null) {
			pluginResponseContext.getMessages().add(
					getMessageSource().getMessage("exception.pluigin.not.loaded", new Object[] {}, Locale.ENGLISH));
			return new ModelAndView("management/error_messages", paramMap);
		}

		/**
		 * Now that plugin is loaded.. Handle subsequent processing to plugin.
		 */

		SwiftUIPluginContext pluginContext = new SwiftUIPluginContext();
		pluginContext.setPluginName(pluginId);
		pluginContext.setHttpServletRequest(httpServletRequest);
		pluginContext.setHttpServletResponse(httpServletResponse);
		pluginContext.setPlatformService(getPlatformService());
		/**
		 * Invoke Plugin Action
		 */
		Method actionMethod = null;
		Object returnValue = invokePluginMethod(swiftUIPlugin, swiftUIPlugin.getClass(), pluginAction,
				new Class[] { SwiftUIPluginContext.class }, new Object[] { pluginContext }, actionMethod);

		ServletWebRequest webRequest = new ServletWebRequest(httpServletRequest, httpServletResponse);

		Object body = null;
		try {
			body = AnnotationUtils.findAnnotation(actionMethod, ResponseBody.class);
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			//e1.printStackTrace();
		}
		if (body != null) {
			try {
				handleResponseBody(returnValue, webRequest);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		else if (returnValue instanceof ModelAndView) {
			return (ModelAndView) returnValue;
		}

		return null;

	}

	/**
	 * @param webRequest
	 * @return
	 * @throws Exception
	 */
	protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		return createHttpInputMessage(servletRequest);
	}

	/**
	 * @param webRequest
	 * @return
	 * @throws Exception
	 */
	protected HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) throws Exception {
		HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse();
		return createHttpOutputMessage(servletResponse);
	}

	/**
	 * @param returnValue
	 * @param webRequest
	 * @throws Exception
	 */
	private void handleResponseBody(Object returnValue, ServletWebRequest webRequest)
			throws Exception {
		if (returnValue == null) {
			return;
		}
		HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
		HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
		writeWithMessageConverters(returnValue, inputMessage, outputMessage);
	}

	/**
	 * @param returnValue
	 * @param inputMessage
	 * @param outputMessage
	 * @throws IOException
	 * @throws HttpMediaTypeNotAcceptableException
	 */
	@SuppressWarnings("unchecked")
	private void writeWithMessageConverters(Object returnValue,
			HttpInputMessage inputMessage, HttpOutputMessage outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException {
		List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
		if (acceptedMediaTypes.isEmpty()) {
			acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
		}
		MediaType.sortByQualityValue(acceptedMediaTypes);
		Class<?> returnValueType = returnValue.getClass();
		List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
		if (getMessageConverters() != null) {
			for (MediaType acceptedMediaType : acceptedMediaTypes) {
				for (HttpMessageConverter messageConverter : getMessageConverters()) {
					if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
						messageConverter.write(returnValue, acceptedMediaType, outputMessage);
						if (logger.isDebugEnabled()) {
							MediaType contentType = outputMessage.getHeaders().getContentType();
							if (contentType == null) {
								contentType = acceptedMediaType;
							}
							logger.debug("Written [" + returnValue + "] as \"" + contentType +
									"\" using [" + messageConverter + "]");
						}
						return;
					}
				}
			}
			for (HttpMessageConverter messageConverter : messageConverters) {
				allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
			}
		}
		throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
	}

	/**
	 * @param clazz
	 * @param actionName
	 * @param argTypes
	 * @param argValues
	 * @param actionMethod
	 * @return
	 */
	public Object invokePluginMethod(AbstractSwiftUIPlugin swiftUIPlugin, Class clazz, String actionName,
			Class[] argTypes, Object[] argValues, Method actionMethod) {
		Object returnValue = null;
		boolean actionFound = false;
		try {
			/**
			 * Read Plugin methods...
			 */
			Method[] methods = clazz.getMethods();

			for (int i = 0; i < methods.length; i++) {
				RequestMapping requestMapping = (RequestMapping) methods[i].getAnnotation(RequestMapping.class);
				if (requestMapping != null && requestMapping.value() != null) {
					String mappingString = requestMapping.value()[0];

					if (StringUtils.startsWithIgnoreCase(mappingString, "/")) {
						mappingString = mappingString.substring(1);
					}

					if (StringUtils.equalsIgnoreCase(mappingString, actionName)) {
						actionFound = true;

						/**
						 * Invoke InitMethod before calling the Plugin Action
						 * method
						 */

						try {
							Method initMethod = clazz.getMethod("initPlugin",
									new Class[] { SwiftUIPluginContext.class });
							initMethod.invoke(swiftUIPlugin, argValues);
						} catch (Exception e) {
							logger.debug(
									"Exception while calling InitPlugin for " + clazz.getName() + "/" + actionName, e);
						}
						actionMethod = methods[i];
						returnValue = methods[i].invoke(swiftUIPlugin, argValues);
						break;
					}
				}

			}

		} catch (Exception e) {
			e.printStackTrace();
			logger.debug("Exception while Invoking Plugin action method for url:" + clazz.getName() + "/" + actionName,
					e);
		} finally {
			if (!actionFound) {
				logger.debug("No Method found with actionName :" + clazz.getName() + "/" + actionName);
			}
		}

		return returnValue;
	}

}
